package Client;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.net.*;
import java.util.ArrayList;
import java.io.*;

import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.xml.sax.InputSource;

// Client class to let a user play Tic-Tac-Toe with
// another user across a network.
public class EaTGClient extends JFrame implements Runnable, KeyListener,
		MouseListener, MouseMotionListener {

	/**
     * 
     */
	private static final long serialVersionUID = 1L;
	public JTextArea display;
	private JPanel boardPanel, panel2;
	private Socket connection;
	private BufferedReader input;
	private PrintWriter output;
	private Thread outputThread;
	public ArrayList<Ammo> ammolist = new ArrayList<Ammo>();
	public ArrayList<Player> players = new ArrayList<Player>();
	public ArrayList<String> ammoQue = new ArrayList<String>();
	public Player me = null;
	public boolean connectionBroke = false;
	public static TileHandler th = new TileHandler();
	public static EaTGClient game = null;
	public DynamicObjectsHandler boo = null;
	public int fpsLimit = 300;
	public int moveSpeed = 100;
	public boolean wait = true;
	public String IP, Port;
	public GribonnsAPI a = new GribonnsAPI();
	public BufferedImage screen = null;
	public int mouseX = 0, mouseY = 0;

	public class PainterThread implements Runnable {

		@Override
		public void run() {
			while (true) {
				if (boo != null) {
					repaint();
				}
				if (fpsLimit > 0) {
					try {
						Thread.sleep(1000 / fpsLimit);
					} catch (InterruptedException ie) {
					}
				}
			}
		}
	}

	public class IPConnector extends javax.swing.JFrame {

		/** Creates new form IPConnector */
		public IPConnector() {
			initComponents();
		}

		/**
		 * This method is called from within the constructor to initialize the
		 * form. WARNING: Do NOT modify this code. The content of this method is
		 * always regenerated by the Form Editor.
		 */
		@SuppressWarnings("unchecked")
		// <editor-fold defaultstate="collapsed" desc="Generated Code">
		private void initComponents() {

			jLabel1 = new javax.swing.JLabel();
			jTextField1 = new javax.swing.JTextField();
			jButton1 = new javax.swing.JButton();
			jLabel2 = new javax.swing.JLabel();
			jTextField2 = new javax.swing.JTextField();
			jButton2 = new javax.swing.JButton();

			setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
			setTitle("Server Connector");

			jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
			jLabel1.setText("Server:");

			jButton1.setText("Cancel");
			jButton1.addActionListener(new java.awt.event.ActionListener() {

				public void actionPerformed(java.awt.event.ActionEvent evt) {
					jButton1ActionPerformed(evt);
				}
			});

			setResizable(false);

			jTextField1.setText("5.219.159.58");
			jTextField2.setText("5000");

			jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
			jLabel2.setText("Port:");

			jButton2.setText("Connect");
			jButton2.addActionListener(new java.awt.event.ActionListener() {

				public void actionPerformed(java.awt.event.ActionEvent evt) {
					jButton2ActionPerformed(evt);
				}
			});

			javax.swing.GroupLayout layout = new javax.swing.GroupLayout(
					getContentPane());
			getContentPane().setLayout(layout);
			layout.setHorizontalGroup(layout
					.createParallelGroup(
							javax.swing.GroupLayout.Alignment.LEADING)
					.addGroup(
							layout.createSequentialGroup()
									.addContainerGap()
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.LEADING,
													false)
													.addGroup(
															layout.createSequentialGroup()
																	.addComponent(
																			jLabel1,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			56,
																			javax.swing.GroupLayout.PREFERRED_SIZE)
																	.addPreferredGap(
																			javax.swing.LayoutStyle.ComponentPlacement.RELATED)
																	.addComponent(
																			jTextField1,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			177,
																			javax.swing.GroupLayout.PREFERRED_SIZE))
													.addGroup(
															javax.swing.GroupLayout.Alignment.TRAILING,
															layout.createSequentialGroup()
																	.addGap(5,
																			5,
																			5)
																	.addComponent(
																			jButton1,
																			javax.swing.GroupLayout.DEFAULT_SIZE,
																			javax.swing.GroupLayout.DEFAULT_SIZE,
																			Short.MAX_VALUE)
																	.addPreferredGap(
																			javax.swing.LayoutStyle.ComponentPlacement.RELATED)
																	.addComponent(
																			jButton2,
																			javax.swing.GroupLayout.DEFAULT_SIZE,
																			113,
																			Short.MAX_VALUE))
													.addGroup(
															layout.createSequentialGroup()
																	.addComponent(
																			jLabel2,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			56,
																			javax.swing.GroupLayout.PREFERRED_SIZE)
																	.addPreferredGap(
																			javax.swing.LayoutStyle.ComponentPlacement.RELATED)
																	.addComponent(
																			jTextField2,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			177,
																			javax.swing.GroupLayout.PREFERRED_SIZE)))
									.addContainerGap(
											javax.swing.GroupLayout.DEFAULT_SIZE,
											Short.MAX_VALUE)));
			layout.setVerticalGroup(layout
					.createParallelGroup(
							javax.swing.GroupLayout.Alignment.LEADING)
					.addGroup(
							layout.createSequentialGroup()
									.addContainerGap()
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.BASELINE)
													.addComponent(jLabel1)
													.addComponent(
															jTextField1,
															javax.swing.GroupLayout.PREFERRED_SIZE,
															javax.swing.GroupLayout.DEFAULT_SIZE,
															javax.swing.GroupLayout.PREFERRED_SIZE))
									.addPreferredGap(
											javax.swing.LayoutStyle.ComponentPlacement.RELATED)
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.BASELINE)
													.addComponent(jLabel2)
													.addComponent(
															jTextField2,
															javax.swing.GroupLayout.PREFERRED_SIZE,
															javax.swing.GroupLayout.DEFAULT_SIZE,
															javax.swing.GroupLayout.PREFERRED_SIZE))
									.addGap(15, 15, 15)
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.BASELINE)
													.addComponent(
															jButton2,
															javax.swing.GroupLayout.PREFERRED_SIZE,
															23,
															javax.swing.GroupLayout.PREFERRED_SIZE)
													.addComponent(
															jButton1,
															javax.swing.GroupLayout.PREFERRED_SIZE,
															23,
															javax.swing.GroupLayout.PREFERRED_SIZE))
									.addContainerGap(
											javax.swing.GroupLayout.DEFAULT_SIZE,
											Short.MAX_VALUE)));

			pack();
		}// </editor-fold>

		private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
			IP = jTextField1.getText();
			Port = jTextField2.getText();
			wait = false;
			dispose();
		}

		private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
			wait = true;
			dispose();
		}

		/**
		 * @param args
		 *            the command line arguments
		 */
		// Variables declaration - do not modify
		private javax.swing.JButton jButton1;
		private javax.swing.JButton jButton2;
		private javax.swing.JLabel jLabel1;
		private javax.swing.JLabel jLabel2;
		private javax.swing.JTextField jTextField1;
		private javax.swing.JTextField jTextField2;
		// End of variables declaration
	}

	public static void main(String args[]) {

		game = new EaTGClient();

		game.new IPConnector().setVisible(true);
		game.wait = true;
		while (game.wait) {
			game.a.sleep(500);
		}
		th = new TileHandler(game);

		game.addWindowListener(new WindowAdapter() {

			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		game.init();
		game.start();
		thHandler();
	}

	public static void thHandler() {
		th.LoadXMLMap();
	}

	// Set up user-interface and board
	public void init() {

		int[] pixels = new int[16 * 16];
		Image image = Toolkit.getDefaultToolkit().createImage(
				new MemoryImageSource(16, 16, pixels, 0, 16));
		Cursor transparentCursor = Toolkit.getDefaultToolkit()
				.createCustomCursor(image, new Point(0, 0), "invisibleCursor");
		Cursor sniperCursor = Toolkit.getDefaultToolkit().createCustomCursor(
				Toolkit.getDefaultToolkit().getImage(
						getClass().getResource("sniper.gif")),
				new Point(16, 16), "invisibleCursor");

		// setCursor(sniperCursor); //TODO set back

		display = new JTextArea(5, 30);
		display.setEditable(false);
		getContentPane().add(new JScrollPane(display), BorderLayout.SOUTH);

		boardPanel = new JPanel();
		GridLayout layout = new GridLayout(3, 3, 0, 0);
		boardPanel.setLayout(layout);

		// id = new JTextField();
		// id.setEditable(true);
		// id.addKeyListener(this);

		// getContentPane().add(id, BorderLayout.NORTH);

		panel2 = th;
		panel2.addMouseMotionListener(this);

		boo = new DynamicObjectsHandler(game);

		setSize(th.windowWidth, th.windowHeight);
		setTitle("EaTG"); // panel2.add(b);
		// boardPanel.setOpaque(false);
		// panel2.add( boardPanel, BorderLayout.CENTER );
		// panel2.add(new board(this), BorderLayout.CENTER);
		// panel2.setOpaque(false);
		// getContentPane().add( th, BorderLayout.CENTER );
		panel2.getInputMap().put(KeyStroke.getKeyStroke("UP"), "pressed1");
		panel2.getInputMap().put(KeyStroke.getKeyStroke("released UP"),
				"released1");
		panel2.getActionMap().put("pressed1", updown);
		panel2.getActionMap().put("released1", upup);
		panel2.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "pressed2");
		panel2.getInputMap().put(KeyStroke.getKeyStroke("released DOWN"),
				"released2");
		panel2.getActionMap().put("pressed2", downdown);
		panel2.getActionMap().put("released2", downup);
		panel2.getInputMap().put(KeyStroke.getKeyStroke("LEFT"), "pressed3");
		panel2.getInputMap().put(KeyStroke.getKeyStroke("released LEFT"),
				"released3");
		panel2.getActionMap().put("pressed3", leftdown);
		panel2.getActionMap().put("released3", leftup);
		panel2.getInputMap().put(KeyStroke.getKeyStroke("RIGHT"), "pressed4");
		panel2.getInputMap().put(KeyStroke.getKeyStroke("released RIGHT"),
				"released4");
		panel2.getActionMap().put("pressed4", rightdown);
		panel2.getActionMap().put("released4", rightup);
		getContentPane().add(boo, BorderLayout.CENTER);
		getContentPane().add(panel2, BorderLayout.CENTER);
		boo.setBackground(new Color(0, 0, 0, 0));
		boo.setSize(th.windowWidth, th.windowHeight);

		panel2.setFocusable(true);
		screen = new BufferedImage(th.windowWidth, th.windowHeight,
				BufferedImage.TYPE_INT_ARGB);
		new Thread(new PainterThread()).start();
		setResizable(false);
		setVisible(true);
	}

	// Make connection to server and get associated streams.
	// Start separate thread to allow this applet to
	// continually update its output in text area display.
	public void start() {
		try {
			connection = new Socket(InetAddress.getByName(IP), a.toInt(Port));
			input = new BufferedReader(new InputStreamReader(
					connection.getInputStream()));
			output = new PrintWriter(connection.getOutputStream(), true);
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			String data = input.readLine();
			System.out.println(data);
			if (data.equals("Too many users")) {
				display.append("Connection refused: server full");
				connection.close();
				return;
			} else {
				try {
					String first = data.split(":")[0];
					th.tileLength = Integer.parseInt(data.split(":")[1]);
					th.spriteWidth = Integer.parseInt(data.split(":")[2]);
					th.spriteHeight = Integer.parseInt(data.split(":")[3]);
					th.data = data.split(":")[4];
					display.append("Your id is set to " + first);
					me = new Player(this, Integer.parseInt(first), "You",
							new Point(20, 20));
					/*
					 * setTitle("EaTG - Map \"" + th.MapFile.replace(".tmx", "")
					 * + "\" (" + IP + ":" + Port + ")");
					 */
				} catch (Exception e) {

				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		outputThread = new Thread(this);
		outputThread.start();

		new Thread(new HandleListening()).start();
	}

	// Control thread that allows continuous update of the
	// text area display.
	public void run() {
		// First get player's mark (X or O)

		// Receive messages sent to client
		while (!connectionBroke) {
			output.println(strPacket(me));
			if (ammoQue.size() > 0) {
				output.println(ammoQue.remove(0));
			}
			// output.println("random: " + System.currentTimeMillis());
			// display.append("send it\n");
			panel2.repaint();
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				break;
			}
		}
	}

	public String strPacket(Player p) {
		return "player:" + p.getName() + ":" + p.getHealth() + ":"
				+ p.getAbsoluteLoc().x + "x" + p.getAbsoluteLoc().y + ":"
				+ p.getAngle();
	}

	public String strAPacket(Ammo a) {
		return "ammo:" + a.getStartLoc().x + "x" + a.getStartLoc().y + ":"
				+ a.getAngle() + ":" + a.getType();
	}

	public boolean containsPlayID(String ide) {
		int id = -1;
		try {
			id = Integer.parseInt(ide);
		} catch (Exception e) {
			return true;
		}
		if (id == -1) {
			return true;
		}
		for (Player p : players) {
			if (p.getId() == id) {
				return true;
			}
		}
		if (me.getId() == id) {
			return true;
		}
		return false;
	}

	public boolean containsAmmoID(String ide) {
		int id = -1;
		try {
			id = Integer.parseInt(ide);
		} catch (Exception e) {
			return true;
		}
		if (id == -1) {
			return true;
		}
		for (Ammo a : ammolist) {
			if (a.getAmmoID() == id) {
				return true;
			}
		}
		return false;
	}

	boolean running = false, canStop = false;
	ArrayList<Integer> KeysPressed = new ArrayList<Integer>();

	public class movePlayer implements Runnable {

		public void run() {
			if (!running) {
				running = true;
				while (!canStop) {
					if (KeysPressed.isEmpty()) {
						canStop = true;
						break;
					}
					ArrayList<Integer> hadKeys = new ArrayList<Integer>();
					try {
						for (Integer keyCode : KeysPressed) {
							if (keyCode != null) {
								if (!hadKeys.contains(keyCode)) {
									hadKeys.add(keyCode);
									me.moveDetect(keyCode);
								}
							}
						}
					} catch (Exception e) {
					}
					try {
						Thread.sleep(1000 / moveSpeed * KeysPressed.size());
					} catch (InterruptedException ex) {
					}
				}
				running = false;
			}
		}
	}

	@Override
	public void keyPressed(KeyEvent event) {
		if (event.getKeyCode() == KeyEvent.VK_SPACE) {
			String ad = strAPacket(me.getAmmoDetails());
			System.out.println("Lol ammo " + ad);
			ammoQue.add(ad);
		} else {
			if (event != null) {
				int ModKey = 0;
				if (event.getKeyCode() == KeyEvent.VK_W) {
					ModKey = KeyEvent.VK_UP;
				} else if (event.getKeyCode() == KeyEvent.VK_A) {
					ModKey = KeyEvent.VK_LEFT;
				} else if (event.getKeyCode() == KeyEvent.VK_S) {
					ModKey = KeyEvent.VK_DOWN;
				} else if (event.getKeyCode() == KeyEvent.VK_D) {
					ModKey = KeyEvent.VK_RIGHT;
				} else {
					ModKey = event.getKeyCode();
				}
				if (!KeysPressed.contains(ModKey)) {
					canStop = false;
					KeysPressed.add(ModKey);
					new Thread(new movePlayer()).start();
				}
			}
		}

	}

	Action updown = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {
			if (!KeysPressed.contains(KeyEvent.VK_UP)) {
				canStop = false;
				KeysPressed.add(KeyEvent.VK_UP);
				new Thread(new movePlayer()).start();
			}
		}
	};

	Action upup = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {

			if (KeysPressed.contains(KeyEvent.VK_UP)) {
				KeysPressed.remove(KeysPressed.indexOf(KeyEvent.VK_UP));
			}
		}
	};

	Action downdown = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {
			if (!KeysPressed.contains(KeyEvent.VK_DOWN)) {
				canStop = false;
				KeysPressed.add(KeyEvent.VK_DOWN);
				new Thread(new movePlayer()).start();
			}
		}
	};

	Action downup = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {

			if (KeysPressed.contains(KeyEvent.VK_DOWN)) {
				KeysPressed.remove(KeysPressed.indexOf(KeyEvent.VK_DOWN));
			}
		}
	};
	Action leftdown = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {
			if (!KeysPressed.contains(KeyEvent.VK_LEFT)) {
				canStop = false;
				KeysPressed.add(KeyEvent.VK_LEFT);
				new Thread(new movePlayer()).start();
			}
		}
	};

	Action leftup = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {

			if (KeysPressed.contains(KeyEvent.VK_LEFT)) {
				KeysPressed.remove(KeysPressed.indexOf(KeyEvent.VK_LEFT));
			}
		}
	};

	Action rightdown = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {
			if (!KeysPressed.contains(KeyEvent.VK_RIGHT)) {
				canStop = false;
				KeysPressed.add(KeyEvent.VK_RIGHT);
				new Thread(new movePlayer()).start();
			}
		}
	};

	Action rightup = new AbstractAction() {
		public void actionPerformed(ActionEvent e) {

			if (KeysPressed.contains(KeyEvent.VK_RIGHT)) {
				KeysPressed.remove(KeysPressed.indexOf(KeyEvent.VK_RIGHT));
			}
		}
	};

	@Override
	public void keyReleased(KeyEvent event) {
		if (event != null) {
			int ModKey = 0;
			if (event.getKeyCode() == KeyEvent.VK_W) {
				ModKey = KeyEvent.VK_UP;
			} else if (event.getKeyCode() == KeyEvent.VK_A) {
				ModKey = KeyEvent.VK_LEFT;
			} else if (event.getKeyCode() == KeyEvent.VK_S) {
				ModKey = KeyEvent.VK_DOWN;
			} else if (event.getKeyCode() == KeyEvent.VK_D) {
				ModKey = KeyEvent.VK_RIGHT;
			} else {
				ModKey = event.getKeyCode();
			}
			if (KeysPressed.contains(ModKey)) {
				KeysPressed.remove(KeysPressed.indexOf(ModKey));
			}
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
	}

	@Override
	public void mouseExited(MouseEvent arg0) {
	}

	@Override
	public void mousePressed(MouseEvent arg0) {
	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
	}

	@Override
	public void mouseDragged(MouseEvent arg0) {
	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
		mouseX = arg0.getX();
		mouseY = arg0.getY();
	}

	EaTGClient gameClient = this;

	public class HandleListening implements Runnable {

		@Override
		public void run() {
			while (true) {
				try {
					String s = input.readLine();
					if (s == null) {
						break;
					}
					if (s.equals("ping")) {
						output.println("pong");
						continue;
					}
					int myid = me.getId();

					if (s.contains(";")) {
						String[] f = s.split(";");
						String first = f[0];
						String[] args = null;
						if (f[1].contains("`")) {
							args = f[1].split("`");
						} else {
							args = new String[] { f[1] };
						}
						// TODO better way to handle different messages
						if (first.equals("ammo")) {

							// ArrayList<Integer> updatedUsers = new
							// ArrayList<Integer>();
							for (String ar : args) {
								String[] arar = ar.split(",");
								// System.out.println(containsAmmoID(arar[1]) +
								// " " + s + " ");
								if (arar[0].equals("destroy")) {
									if (!containsAmmoID(arar[1])) {
										continue;
									}
									for (int u = 0; u < ammolist.size(); u++) {
										if ((ammolist.get(u).getAmmoID() + "")
												.equals(arar[1])) {
											ammolist.remove(u);
										}
									}
								} else {
									if (!containsAmmoID(arar[1])) {

										// System.out.println("new player found");
										String[] pointSplit = arar[2]
												.split(":");
										// players.add(new
										// Player(Integer.parseInt(arar[0]),
										// arar[1], new
										// Point(Integer.parseInt(pointSplit[0]),
										// Integer.parseInt(pointSplit[1]))));
										Ammo a = new Ammo();
										a.setAmmoID(Integer.parseInt(arar[1]));
										a.setStartLoc(new Point(Integer
												.parseInt(pointSplit[0]),
												Integer.parseInt(pointSplit[1])));
										a.setAngle(Integer.parseInt(arar[3]));
										a.setDistance(Integer.parseInt(arar[4]));
										game.ammolist.add(a);

										// System.out.println(ammolist);
									}
								}
							}

						}
						if (first.equals("players")) {
							// ArrayList<Integer> updatedUsers = new
							// ArrayList<Integer>();
							for (String ar : args) {
								String[] arar = ar.split(",");
								if (!containsPlayID(arar[0])) {
									// System.out.println("new player found");
									String[] pointSplit = arar[2].split(":");
									players.add(new Player(
											gameClient,
											Integer.parseInt(arar[0]),
											arar[1],
											new Point(
													Integer.parseInt(pointSplit[0]),
													Integer.parseInt(pointSplit[1]))));
								} else {
									String[] pointSplit = arar[2].split(":");
									for (int i = 0; i < players.size(); i++) {
										if (players.get(i).getId() == myid) {
											me.setLoc(new Point(
													Integer.parseInt(pointSplit[0]),
													Integer.parseInt(pointSplit[1])));
											me.setLatency(Integer
													.parseInt(arar[3]));
										} else if (players.get(i).getId() == Integer
												.parseInt(arar[0])) {
											players.get(i)
													.setLoc(new Point(
															Integer.parseInt(pointSplit[0]),
															Integer.parseInt(pointSplit[1])));
											players.get(i).setLatency(
													Integer.parseInt(arar[3]));
											// TODO set health and stuff
										}
									}
								}
								// updatedUsers.add(Integer.parseInt(arar[0]));
							}
							for (int u = 0; u < players.size(); u++) {
								if (players.get(u).getId() != myid
										&& players.get(u).getDestroy()) {
									players.remove(u);
								}
							}
						}

					}
					// display.append("Received " + s + "\n");
					// display.insert("Received " + s + "\n", 0);
				} catch (IOException e) {
					display.append("Connection broke");
					connectionBroke = true;
					break;
				}
			}
		}
	}
}
